# -*- coding: UTF-8 -*-
"Les balises par défaut, utilisables dans tous les modèles."

import sys
import template

class CommentNode(template.Node):
    def render(self, context):
        return ''

class CycleNode(template.Node):
    def __init__(self, cyclevars):
        self.cyclevars = cyclevars
        self.cyclevars_len = len(cyclevars)
        self.counter = -1

    def render(self, context):
        self.counter += 1
        return self.cyclevars[self.counter % self.cyclevars_len]

class DebugNode(template.Node):
    def render(self, context):
        from pprint import pformat
        output = [pformat(val) for val in context]
        output.append('\n\n')
        output.append(pformat(sys.modules))
        return ''.join(output)

class FilterNode(template.Node):
    def __init__(self, filters, nodelist):
        self.filters, self.nodelist = filters, nodelist

    def render(self, context):
        output = self.nodelist.render(context)
        # on applique les filtres
        for f in self.filters:
            output = template.registered_filters[f[0]][0](output, f[1])
        return output

class FirstOfNode(template.Node):
    def __init__(self, vars):
        self.vars = vars

    def render(self, context):
        for var in self.vars:
            value = template.resolve_variable(var, context)
            if value:
                return str(value)
        return ''

class ForNode(template.Node):
    def __init__(self, loopvar, sequence, reversed, nodelist_loop):
        self.loopvar, self.sequence = loopvar, sequence
        self.reversed = reversed
        self.nodelist_loop = nodelist_loop

    def __repr__(self):
        if self.reversed:
            reversed = ' inversée'
        else:
            reversed = ''
        return "<Nœud For : for %s in %s, nb_nœuds_fils : %d%s>" % \
            (self.loopvar, self.sequence, len(self.nodelist_loop), reversed)

    def __iter__(self):
        for node in self.nodelist_loop:
            yield node

    def get_nodes_by_type(self, nodetype):
        nodes = []
        if isinstance(self, nodetype):
            nodes.append(self)
        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
        return nodes

    def render(self, context):
        nodelist = template.NodeList()
        if context.has_key('forloop'):
            parentloop = context['forloop']
        else:
            parentloop = {}
        context.push()
        try:
            values = template.resolve_variable_with_filters(self.sequence, context)
        except template.VariableDoesNotExist:
            values = []
        if values is None:
            values = []
        len_values = len(values)
        if self.reversed:
            # Tiré de http://www.python.org/doc/current/tut/node11.html
            def reverse(data):
                for index in range(len(data)-1, -1, -1):
                    yield data[index]
            values = reverse(values)
        for i, item in enumerate(values):
            context['forloop'] = {
                # raccourcis pour obtenir le numéro de l'itération actuelle
                'counter0': i,
                'counter': i+1,
                # valeurs booléennes utiles : première itération? dernière itération?
                'first': (i == 0),
                'last': (i == len_values - 1),
                'parentloop': parentloop,
            }
            context[self.loopvar] = item
            for node in self.nodelist_loop:
                nodelist.append(node.render(context))
        context.pop()
        return nodelist.render(context)

class IfChangedNode(template.Node):
    def __init__(self, nodelist):
        self.nodelist = nodelist
        self._last_seen = None

    def render(self, context):
        content = self.nodelist.render(context)
        if content != self._last_seen:
            firstloop = (self._last_seen == None)
            self._last_seen = content
            context.push()
            context['ifchanged'] = {'firstloop': firstloop}
            content = self.nodelist.render(context)
            context.pop()
            return content
        else:
            return ''

class IfNotEqualNode(template.Node):
    def __init__(self, var1, var2, nodelist):
        self.var1, self.var2, self.nodelist = var1, var2, nodelist

    def __repr__(self):
        return "<IfNotEqualNode>"

    def render(self, context):
        if template.resolve_variable(self.var1, context) != template.resolve_variable(self.var2, context):
            return self.nodelist.render(context)
        else:
            return ''

class IfNode(template.Node):
    def __init__(self, boolvars, nodelist_true, nodelist_false):
        self.boolvars = boolvars
        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false

    def __repr__(self):
        return "<Nœud If>"

    def __iter__(self):
        for node in self.nodelist_true:
            yield node
        for node in self.nodelist_false:
            yield node

    def get_nodes_by_type(self, nodetype):
        nodes = []
        if isinstance(self, nodetype):
            nodes.append(self)
        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
        return nodes

    def render(self, context):
        for ifnot, boolvar in self.boolvars:
            try:
                value = template.resolve_variable_with_filters(boolvar, context)
            except template.VariableDoesNotExist:
                value = None
            if (value and not ifnot) or (ifnot and not value):
                return self.nodelist_true.render(context)
        return self.nodelist_false.render(context)

class RegroupNode(template.Node):
    def __init__(self, target_var, expression, var_name):
        self.target_var, self.expression = target_var, expression
        self.var_name = var_name

    def render(self, context):
        obj_list = template.resolve_variable_with_filters(self.target_var, context)
        if obj_list == '': # impossible de trouver target_var dans le contexte actuel; le moteur de rendu échoue silencieusement
            context[self.var_name] = []
            return ''
        output = [] # liste de dictionnaires de la forme {'grouper': clé, 'list': [liste du contenu]}
        for obj in obj_list:
            grouper = template.resolve_variable_with_filters('var.%s' % self.expression, \
                template.Context({'var': obj}))
            if output and repr(output[-1]['grouper']) == repr(grouper):
                output[-1]['list'].append(obj)
            else:
                output.append({'grouper': grouper, 'list': [obj]})
        context[self.var_name] = output
        return ''

def include_is_allowed(filepath):
    from django.conf.settings import ALLOWED_INCLUDE_ROOTS
    for root in ALLOWED_INCLUDE_ROOTS:
        if filepath.startswith(root):
            return True
    return False

class SsiNode(template.Node):
    def __init__(self, filepath, parsed):
        self.filepath, self.parsed = filepath, parsed

    def render(self, context):
        if not include_is_allowed(self.filepath):
            return '' # Le moteur de rendu échoue de manière silencieuse si l'inclusion est interdite.
        try:
            fp = open(self.filepath, 'r')
            output = fp.read()
            fp.close()
        except IOError:
            output = ''
        if self.parsed:
            try:
                t = template.Template(output)
                return t.render(context)
            except template.TemplateSyntaxError:
                return '' # Le moteur de rendu échoue de manière silencieuse si le modèle inclus est invalide.
        return output

class LoadNode(template.Node):
    def __init__(self, taglib):
        self.taglib = taglib

    def load_taglib(taglib):
        return __import__("django.templatetags.%s" % taglib.split('.')[-1], '', '', [''])
    load_taglib = staticmethod(load_taglib)

    def render(self, context):
        "Importe le module approprié."
        try:
            self.__class__.load_taglib(self.taglib)
        except ImportError:
            pass # Le moteur de rendu échoue de manière silencieuse si l'importation déclenche une erreur.
        return ''

class NowNode(template.Node):
    def __init__(self, format_string):
        self.format_string = format_string

    def render(self, context):
        from datetime import datetime
        from django.utils.dateformat import DateFormat
        df = DateFormat(datetime.now())
        return df.format(self.format_string)

class TemplateTagNode(template.Node):
    mapping = {'openblock': template.BLOCK_TAG_START,
               'closeblock': template.BLOCK_TAG_END,
               'openvariable': template.VARIABLE_TAG_START,
               'closevariable': template.VARIABLE_TAG_END}

    def __init__(self, tagtype):
        self.tagtype = tagtype

    def render(self, context):
        return self.mapping.get(self.tagtype, '')

class WidthRatioNode(template.Node):
    def __init__(self, val_var, max_var, max_width):
        self.val_var = val_var
        self.max_var = max_var
        self.max_width = max_width

    def render(self, context):
        try:
            value = template.resolve_variable_with_filters(self.val_var, context)
            maxvalue = template.resolve_variable_with_filters(self.max_var, context)
        except template.VariableDoesNotExist:
            return ''
        try:
            value = float(value)
            maxvalue = float(maxvalue)
            ratio = (value / maxvalue) * int(self.max_width)
        except (ValueError, ZeroDivisionError):
            return ''
        return str(int(round(ratio)))

def do_comment(parser, token):
    """
    Le moteur de rendu des modèles ignore tout entre ``{% comment %}`` et ``{% endcomment %}``.
    """
    nodelist = parser.parse(('endcomment',))
    parser.delete_first_token()
    return CommentNode()

def do_cycle(parser, token):
    """
    Le moteur de rendu des modèles alterne entre les chaînes spécifiées à chaque fois qu'il
    rencontre cette balise.

    Au sein d'une boucle, alterne entre les chaînes spécifiées à chaque
    itération de la boucle :

    ::

        {% for o in une_liste %}
            <tr class="{% cycle ligne1,ligne2 %}">
            ...
            </tr>
        {% endfor %}

    À l'extérieur d'une boucle, donnez aux valeurs un nom unique la première
    fois puis utilisez ce nom à chaque appel successif :

    ::

            <tr class="{% cycle ligne1,ligne2,ligne3 as couleurs %}">...</tr>
            <tr class="{% cycle couleurs %}">...</tr>
            <tr class="{% cycle couleurs %}">...</tr>

    Vous pouvez utiliser autant de valeurs que voulu, chaque valeur étant séparée
    des autres par une virgule. Assurez-vous de ne pas placer d'espaces entre les
    valeurs, seulement des virgules.
    """

    # Note : Cette fonction renvoie le même objet Node à chaque appel provoqué par
    # une balise {% cycle nom %}; c'est-à-dire que l'objet Node renvoyé par cette
    # fonction lors d'un appel provoqué par {% cycle a,b,c as nom %} et l'objet
    # Node renvoyé pour {% cycle nom %} sont exactement les mêmes. Ceci ne devrait
    # pas poser de problème (ne *devrait* pas) mais si cela en pose, vous voilà
    # averti.
    #
    # Avertissement concernant une pratique affreuse : cette fonction garde une trace
    # des noms utilisés par les balises 'cycle' au moyen d'un dictionnaire stocké en
    # tant qu'attribut de l'analyseur syntaxique actuel; en conséquence, les noms
    # au sein d'un même modèle doivent être distincts (alors que l'usage d'une
    # variable globale pour ce dictionnaire aurait rendu obligatoire l'usage de noms
    # distincts pour *tous* les modèles).

    args = token.contents.split()
    if len(args) < 2:
        raise template.TemplateSyntaxError("La balise 'cycle' nécessite au moins deux arguments.")

    elif len(args) == 2 and "," in args[1]:
        # {% cycle a,b,c %}
        cyclevars = [v for v in args[1].split(",") if v]    # on scinde la chaîne et on élimine les valeurs vierges
        return CycleNode(cyclevars)
        # {% cycle nom %}

    elif len(args) == 2:
        name = args[1]
        if not parser._namedCycleNodes.has_key(name):
            raise template.TemplateSyntaxError("Le nom '%s' utilisé dans la balise 'cycle' n'existe pas." % name)
        return parser._namedCycleNodes[name]

    elif len(args) == 4:
        # {% cycle a,b,c as nom %}
        if args[2] != 'as':
            raise template.TemplateSyntaxError("Le deuxième argument passé à une balise 'cycle' doit être 'as'.")
        cyclevars = [v for v in args[1].split(",") if v]    # on scinde la chaîne et on élimine les valeurs vierges
        name = args[3]
        node = CycleNode(cyclevars)

        if not hasattr(parser, '_namedCycleNodes'):
            parser._namedCycleNodes = {}

        parser._namedCycleNodes[name] = node
        return node

    else:
        raise template.TemplateSyntaxError("Arguments invalides passés à la balise 'cycle' : %s." % args)

def do_debug(parser, token):
    "Affiche une grande quantité d'informations utiles pour le débogage, y compris le contexte actuel et les modules importés."
    return DebugNode()

def do_filter(parser, token):
    """
    Filtre le contenu du bloc en utilisant les filtres spécifiés.

    Vous pouvez enchaîner plusieurs filtres en les séparant par des '|'; les filtres
    peuvent se voir passer des arguments (tout comme pour la syntaxe
    des variables).

    Exemple :

    ::

        {% filter escape|lower %}
            Tous les caractères spéciaux de ce texte seront échappés pour être
            acceptés par HTML et ce texte apparaîtra en minuscules.
        {% endfilter %}
    """
    _, rest = token.contents.split(None, 1)
    _, filters = template.get_filters_from_token('var|%s' % rest)
    nodelist = parser.parse(('endfilter',))
    parser.delete_first_token()
    return FilterNode(filters, nodelist)

def do_firstof(parser, token):
    """
    Affiche la première variable spécifiée qui n'a pas une valeur fausse.

    N'affiche rien si toutes les variables spécifiées ont une valeur fausse.

    Par exemple :

    ::

        {% firstof var1 var2 var3 %}

    équivaut à :

    ::

        {% if var1 %}
            {{ var1 }}
        {% else %}{% if var2 %}
            {{ var2 }}
        {% else %}{% if var3 %}
            {{ var3 }}
        {% endif %}{% endif %}{% endif %}

    mais est quand même un peu plus clair!
    """
    bits = token.contents.split()[1:]
    if len(bits) < 1:
        raise template.TemplateSyntaxError, "La balise 'firstof' nécessite au moins un argument."
    return FirstOfNode(bits)


def do_for(parser, token):
    """
    Itère sur les éléments d'un tableau.

    À titre d'exemple, pour afficher une liste d'athlètes nommée ``liste_athletes`` :

    ::

        <ul>
        {% for athlete in liste_athletes %}
            <li>{{ athlete.nom }}</li>
        {% endfor %}
        </ul>

    Vous pouvez aussi itérer à l'envers sur une liste en employant ``{% for objet in liste reversed %}``.

    La boucle for définit un certain nombre de variables qui sont disponibles
    au sein de la boucle :

        ==========================  ==============================================================================
        Variable                    Description
        ==========================  ==============================================================================
        ``forloop.counter``         L'itération actuelle de la boucle (les indices commencent à 1)
        ``forloop.counter0``        L'itération actuelle de la boucle (les indices commencent à 0)
        ``forloop.first``           True si c'est la première itération de la boucle
        ``forloop.last``            True si c'est la dernière itération de la boucle
        ``forloop.parentloop``      Pour les boucles imbriquées les unes dans les autres, cette variable désigne
                                    la boucle "au-dessus" de la boucle actuelle
        ==========================  ==============================================================================

    """
    bits = token.contents.split()
    if len(bits) == 5 and bits[4] != 'reversed':
        raise TemplateSyntaxError, "Une balise 'for' accompagnée de cinq arguments doit avoir pour dernier argument 'reversed' : %s." % token.contents
    if len(bits) not in (4, 5):
        raise TemplateSyntaxError, "Les balises 'for' acceptent quatre ou cinq arguments : %s." % token.contents
    if bits[2] != 'in':
        raise template.TemplateSyntaxError, "Le deuxième argument d'une balise 'for' doit être 'in' : %s" % token.contents
    loopvar = bits[1]
    sequence = bits[3]
    reversed = (len(bits) == 5)
    nodelist_loop = parser.parse(('endfor',))
    parser.delete_first_token()
    return ForNode(loopvar, sequence, reversed, nodelist_loop)

def do_ifnotequal(parser, token):
    """
    Affiche le contenu du bloc si les deux arguments ne sont pas égaux l'un à l'autre.

    Exemple :

    ::

        {% ifnotequal utilisateur.id commentaire.id_utilisateur %}
            ...
        {% endifnotequal %}
    """
    bits = token.contents.split()
    if len(bits) != 3:
        raise template.TemplateSyntaxError, "La balise 'ifnotequal' accepte deux arguments."
    nodelist = parser.parse(('endifnotequal',))
    parser.delete_first_token()
    return IfNotEqualNode(bits[1], bits[2], nodelist)

def do_if(parser, token):
    """
    La balise ``{% if %}`` évalue une variable, et si cette variable est considérée
    comme "vraie" (c'est-à-dire si cette variable existe, si elle n'est pas vide et
    si elle n'a pas une valeur considérée comme fausse par Python) alors le contenu
    du bloc est affiché :

    ::

        {% if liste_athletes %}
            Nombre d'athlètes : {{ liste_athletes|count }}.
        {% else %}
            Aucun athlète.
        {% endif %}

    Dans le code ci-dessus, si la liste ``liste_athletes`` n'est pas vide le nombre
    d'athlètes sera affiché à la place de la variable ``{{ liste_athletes|count }}``.

    Comme vous pouvez le constater, la balise ``if`` accepte aussi une clause
    ``{% else %}`` facultative qui détermine ce que l'on doit afficher en cas
    d'échec du test.

    Les balises ``if`` peuvent utiliser les mots-clés ``or`` ou ``not`` afin
    de tester plusieurs variables à la fois ou afin d'inverser le test pour une
    variable particulière :

    ::

        {% if not liste_athletes %}
            Il n'y a pas d'athlètes.
        {% endif %}

        {% if liste_athletes or liste_entraineurs %}
            Il y a des athlètes ou des entraîneurs.
        {% endif %}

        {% if not liste_athletes or liste_entraineurs %}
            Il n'y a pas d'athlètes ou il y a des entraîneurs (bon, OK,
            j'avoue, la transcription de tests booléens en français donne
            un résultat tout à fait stupide; ce n'est pas ma faute).
        {% endif %}

    Pour plus de simplicité, les balises ``if`` n'acceptent pas les clauses ``and``;
    utilisez à la place des balises ``if`` imbriquées les unes dans les autres :

    ::

        {% if liste_athletes %}
            {% if liste_entraineurs %}
            Nombre d'athlètes : {{ liste_athletes|count }}.
            Nombre d'entraîneurs : {{ liste_entraineurs|count }}.
            {% endif %}
        {% endif %}
    """
    bits = token.contents.split()
    del bits[0]
    if not bits:
        raise template.TemplateSyntaxError, "La balise 'if' nécessite au moins un argument."
    # on a maintenant une séquence bits qui ressemble à ça : ['a', 'or', 'not', 'b', 'or', 'c.d']
    boolpairs = ' '.join(bits).split(' or ')
    boolvars = []
    for boolpair in boolpairs:
        if ' ' in boolpair:
            not_, boolvar = boolpair.split()
            if not_ != 'not':
                raise template.TemplateSyntaxError, "Mot-clé 'not' attendu dans la balise 'if'."
            boolvars.append((True, boolvar))
        else:
            boolvars.append((False, boolpair))
    nodelist_true = parser.parse(('else', 'endif'))
    token = parser.next_token()
    if token.contents == 'else':
        nodelist_false = parser.parse(('endif',))
        parser.delete_first_token()
    else:
        nodelist_false = template.NodeList()
    return IfNode(boolvars, nodelist_true, nodelist_false)

def do_ifchanged(parser, token):
    """
    Teste si une valeur a changé depuis la dernière itération d'une boucle.

    La balise-bloc 'ifchanged' est utilisée au sein d'une boucle. Elle teste si
    le rendu du contenu de la balise pour cette itération est différent du rendu
    généré pour l'itération précédente et ne l'affiche que si le rendu a changé
    depuis la dernière itération :

    ::

        <h1>Archive des messages de l'année {{ annee }}</h1>

        {% for date in jours %}
        {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
        <a href="{{ date|date:"d M"|lower }}/">{{ date|date:"j" }}</a>
        {% endfor %}
    """
    bits = token.contents.split()
    if len(bits) != 1:
        raise template.TemplateSyntaxError, "La balise 'ifchanged' n'accepte aucun argument."
    nodelist = parser.parse(('endifchanged',))
    parser.delete_first_token()
    return IfChangedNode(nodelist)

def do_ssi(parser, token):
    """
    Affiche le contenu du fichier spécifié au sein de la page.

    Tout comme une balise "include" classique, la balise ``ssi`` inclut à
    l'intérieur de la page actuelle le contenu d'un autre fichier (fichier
    devant être spécifié à l'aide d'un chemin d'accès absolu) :

    ::

        {% ssi /home/html/ljworld.com/includes/right_generic.html %}

    Si le paramètre facultatif "parsed" est spécifié, le contenu du fichier inclus
    sera en plus évalué en tant que modèle, en utilisant le contexte actuel :

    ::

        {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
    """
    bits = token.contents.split()
    parsed = False
    if len(bits) not in (2, 3):
        raise template.TemplateSyntaxError, "La balise 'ssi' n'accepte qu'un argument : le chemin d'accès du fichier devant être inclus."
    if len(bits) == 3:
        if bits[2] == 'parsed':
            parsed = True
        else:
            raise template.TemplateSyntaxError, "Le deuxième argument (facultatif) devant être passé à la balise '%s' doit être 'parsed'." % bits[0]
    return SsiNode(bits[1], parsed)

def do_load(parser, token):
    """
    Charge une série de balises personnalisées.

    À titre d'exemple, pour charger les balises se trouvant dans ``django/templatetags/news/photos.py`` :

    ::

        {% load news.photos %}
    """
    bits = token.contents.split()
    if len(bits) != 2:
        raise template.TemplateSyntaxError, "La balise 'load' n'accepte qu'un argument."
    taglib = bits[1]
    # on vérifie au moment de la compilation du modèle que le module correspondant peut être importé
    try:
        LoadNode.load_taglib(taglib)
    except ImportError:
        raise template.TemplateSyntaxError, "'%s' ne désigne pas une bibliothèque de balises valide." % taglib
    return LoadNode(taglib)

def do_now(parser, token):
    """
    Affiche la date du jour, formatée en fonction de la chaîne spécifiée.

    Utilisez le même format que celui de la fonction ``date()`` de PHP; voyez
    http://php.net/date pour une liste de toutes les valeurs possibles.

    Exemple d'utilisation :

    ::

        Nous sommes le {% now "j F Y" %}; il est {% now "H:i" %}.
    """
    bits = token.contents.split('"')
    if len(bits) != 3:
        raise template.TemplateSyntaxError, "La balise 'now' n'accepte qu'un argument."
    format_string = bits[1]
    return NowNode(format_string)

def do_regroup(parser, token):
    """
    Regroupe une liste d'objets apparentés en fonction d'un seul et même attribut.

    Pour illustrer l'usage de cette balise assez complexe, le mieux est de passer par un exemple : admettons que
    ``personnes`` constitue une liste d'objets ``Personne``; chaque objet ``Personne``
    posséde les attributs ``prenom``, ``nom`` et ``sexe``. Supposons maintenant que vous
    aimeriez afficher une liste du genre :

        * Homme :
            * George Bush
            * Bill Clinton
        * Femme :
            * Margaret Thatcher
            * Colendeeza Rice
        * Autre :
            * Janet Reno

    Le morceau de code suivant accomplira cette tâche ingrate :

    ::

        {% regroup personnes by sexe as groupes %}
        <ul>
        {% for groupe in groupes %}
            <li>{{ groupe.grouper }}
            <ul>
                {% for elem in groupe.list %}
                <li>{{ elem }}</li>
                {% endfor %}
            </ul>
        {% endfor %}
        </ul>

    Comme vous pouvez le constater, ``{% regroup %}`` crée une liste spéciale
    d'objets possédant deux attributs : ``grouper`` et ``list``. L'attribut ``grouper``
    contient l'élément à partir duquel le regroupement a été effectué; ``list``
    contient la liste des objets ayant l'élément ``grouper`` en commun. Dans notre
    cas, ``grouper`` sera tour-à-tour ``Homme``, ``Femme`` et ``Inconnu``; quant à
    l'attribut ``list``, il constituera tour-à-tour la liste des personnes du sexe
    correspondant.

    Notez que `{% regroup %}`` ne fonctionnera pas lorsque la liste à partir de
    laquelle effectuer le regroupement n'est pas elle-même triée par la clé
    désirée! Cela signifie que si la liste ``personnes`` n'est pas triée par sexe,
    vous devrez la trier avant d'effectuer le regroupement :

    ::

        {% regroup personnes|dictsort:"sexe" by sexe as groupes %}

    """
    firstbits = token.contents.split(None, 3)
    if len(firstbits) != 4:
        raise template.TemplateSyntaxError, "La balise 'regroup' nécessite cinq arguments."
    target_var = firstbits[1]
    if firstbits[2] != 'by':
        raise template.TemplateSyntaxError, "Le deuxième argument d'une balise 'regroup' doit être 'by'."
    lastbits_reversed = firstbits[3][::-1].split(None, 2)
    if lastbits_reversed[1][::-1] != 'as':
        raise template.TemplateSyntaxError, "L'avant-dernier argument d'une balise 'regroup' doit être 'as'."
    expression = lastbits_reversed[2][::-1]
    var_name = lastbits_reversed[0][::-1]
    return RegroupNode(target_var, expression, var_name)

def do_templatetag(parser, token):
    """
    Affiche l'une des chaînes de caractère servant à délimiter des balises dans
    les modèles Django.

    Comme les modèles Django n'ont aucune idée du concept d'échappement, pour
    afficher l'une des chaînes de caractère servant à délimiter des balises en
    Django vous devez utiliser la balise ``{% templatetag %}``.

    L'argument spécifié indique à cette balise la chaîne à afficher :

        ==================  =======
        Argument            Affiche
        ==================  =======
        ``openblock``       ``{%``
        ``closeblock``      ``%}``
        ``openvariable``    ``{{``
        ``closevariable``   ``}}``
        ==================  =======
    """
    bits = token.contents.split()
    if len(bits) != 2:
        raise template.TemplateSyntaxError, "La balise 'templatetag' n'accepte qu'un argument."
    tag = bits[1]
    if not TemplateTagNode.mapping.has_key(tag):
        raise template.TemplateSyntaxError, "Argument invalide passé à la balise 'templatetag' : '%s'. L'argument peut prendre l'une de ces valeurs : %s" % \
            (tag, TemplateTagNode.mapping.keys())
    return TemplateTagNode(tag)

def do_widthratio(parser, token):
    """
    Pour créer des diagrammes en barre et autres graphiques, cette balise calcule
    la fraction arg1/arg2 puis multiplie le résultat par arg3; le tout est arrondi
    à l'entier le plus proche.

    À titre d'exemple :

    ::

        <img src='bar.gif' height='10' width='{% widthratio cette_valeur valeur_max 100 %}' />

    Si ``cette_valeur`` vaut 175 et ``valeur_max`` 200, alors l'image ci-dessus
    aura une longueur de 88 pixels (car 175/200 = .875; .875 * 100 = 87.5; arrondi
    à 88).
    """
    bits = token.contents.split()
    if len(bits) != 4:
        raise template.TemplateSyntaxError("La balise 'widthratio' nécessite trois arguments.")
    tag, this_value_var, max_value_var, max_width = bits
    try:
        max_width = int(max_width)
    except ValueError:
        raise template.TemplateSyntaxError("Le dernier argument passé à la balise 'widthratio' doit être un entier.")
    return WidthRatioNode(this_value_var, max_value_var, max_width)

template.register_tag('comment', do_comment)
template.register_tag('cycle', do_cycle)
template.register_tag('debug', do_debug)
template.register_tag('filter', do_filter)
template.register_tag('firstof', do_firstof)
template.register_tag('for', do_for)
template.register_tag('ifnotequal', do_ifnotequal)
template.register_tag('if', do_if)
template.register_tag('ifchanged', do_ifchanged)
template.register_tag('regroup', do_regroup)
template.register_tag('ssi', do_ssi)
template.register_tag('load', do_load)
template.register_tag('now', do_now)
template.register_tag('templatetag', do_templatetag)
template.register_tag('widthratio', do_widthratio)
